﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Seagull.Configuration;
using Seagull.Events;
using Seagull.Events.Data;
using Seagull.Generic;
using Seagull.Model;
using Seagull.Model.Alerts;

namespace Seagull.Shell.Utils
{
    /// <summary>
    /// 监听 NewFrameEvent 并记录各个通道的最后数据时间。定期检查是否有通道发生
    /// </summary>
    internal class RealtimeDataMonitor : IModule
    {
        #region Singleton

        private static readonly Lazy<RealtimeDataMonitor> _instance =
            new Lazy<RealtimeDataMonitor>(() => new RealtimeDataMonitor());

        public static RealtimeDataMonitor Instance
        {
            get { return _instance.Value; }
        }

        #endregion

        private const int AlertCheckInterval = 10000;

        private readonly Timer _timer;
        private RegistrationToken _eventRegistrationToken;

        private readonly ConcurrentDictionary<byte, DateTime> _lastFrameTimeOfChannel =
            new ConcurrentDictionary<byte, DateTime>(2, Config.Instance.ChannelConfig.Count + 1);
        private readonly ConcurrentDictionary<byte, DateTime> _lastAlertTimeOfChannel =
            new ConcurrentDictionary<byte, DateTime>(2, Config.Instance.ChannelConfig.Count + 1);

        private RealtimeDataMonitor()
        {
            _timer = new Timer(OnTimerTick);
        }

        /// <summary>
        /// 返回上一次报警的时间。
        /// </summary>
        public DateTime LastAlertTime
        {
            get { return _lastAlertTimeOfChannel.Select(t => t.Value).DefaultIfEmpty(DateTime.MinValue).Max(); }
        }

        public void Start()
        {
            _timer.Change(AlertCheckInterval, AlertCheckInterval);
            _eventRegistrationToken = NewFrameEvent.Instance.Register(OnNewFrame);
        }

        public void Stop()
        {
            NewFrameEvent.Instance.UnRegister(_eventRegistrationToken);
            _timer.Change(-1, -1);
        }

        /// <summary>
        /// 每当收到新的帧，更新通道的最后帧时间。
        /// </summary>
        /// <param name="frame"></param>
        private void OnNewFrame(Frame frame)
        {
            _lastFrameTimeOfChannel.AddOrUpdate(frame.Channel, DateTime.Now, (b, time) => DateTime.Now);
        }

        /// <summary>
        /// 定时检查是否要报警。
        /// </summary>
        /// <param name="state"></param>
        private void OnTimerTick(object state)
        {
            DateTime lastFrameTimeForAllNoDataChannels;
            var alertChannels = GetNoDataAlertChannels(ChannelsToCheckNoDataAlert, out lastFrameTimeForAllNoDataChannels).ToArray();

            if(alertChannels.Any())
            {
                AlertEvent.Instance.Publish(new NoDataAlert(alertChannels, lastFrameTimeForAllNoDataChannels));
            }
        }

        /// <summary>
        /// 取得无数据的通道列表，同时返回这些通道最后一帧的时间。
        /// </summary>
        /// <param name="channelsToCheck"></param>
        /// <param name="lastFrameTimeForAllNoDataChannels"></param>
        /// <returns></returns>
        private IEnumerable<byte> GetNoDataAlertChannels(
            IEnumerable<byte> channelsToCheck, out DateTime lastFrameTimeForAllNoDataChannels)
        {
            var alertChannels = new List<byte>();
            lastFrameTimeForAllNoDataChannels = DateTime.MinValue;
            foreach(var channel in channelsToCheck)
            {
                var lastFrameTime = _lastFrameTimeOfChannel.TryGetValue(channel);
                var lastAlertTime = _lastAlertTimeOfChannel.TryGetValue(channel);
                if(ShouldLogAlert(lastFrameTime, lastAlertTime))
                {
                    _lastAlertTimeOfChannel[channel] = DateTime.Now;
                    alertChannels.Add(channel);
                    
                    // 更新最后一帧的时间
                    if(lastFrameTime.IsSome && lastFrameTime.Value > lastFrameTimeForAllNoDataChannels)
                    {
                        lastFrameTimeForAllNoDataChannels = lastFrameTime.Value;
                    }
                }
            }
            return alertChannels;
        }

        /// <summary>
        /// 取得需要进行无数据检查的通道列表。
        /// </summary>
        private static IEnumerable<byte> ChannelsToCheckNoDataAlert
        {
            get
            {
                var channelsToCheck = new List<byte> { Channel.PhaseChannel };
                Config.Instance.ChannelConfig.ForAllChannels(channel =>
                {
                    if(Config.Instance.ChannelConfig[channel].NoDataAlert)
                    {
                        channelsToCheck.Add(channel);
                    }
                });
                return channelsToCheck;
            }
        }

        /// <summary>
        /// 检查是否无数据并且需要再次记录该情况。
        /// </summary>
        /// <param name="lastFrameTime"></param>
        /// <param name="lastLogAlertTime"></param>
        /// <returns></returns>
        private static bool ShouldLogAlert(Optional<DateTime> lastFrameTime, Optional<DateTime> lastLogAlertTime)
        {
            return
                // 无数据？
                (lastFrameTime.IsNone && UpTime.TotalSeconds >
                    Config.Instance.DataReadConfig.NoRealtimeDataAlertDurationSeconds ||
                (DateTime.Now - lastFrameTime.Value).TotalSeconds >
                    Config.Instance.DataReadConfig.NoRealtimeDataAlertDurationSeconds) &&
                // 已经需要再次记录？
                (lastLogAlertTime.IsNone && UpTime.TotalSeconds >
                    Config.Instance.DataReadConfig.NoRealtimeDataAlertDurationSeconds ||
                (DateTime.Now - lastLogAlertTime.Value).TotalSeconds >
                    Config.Instance.DataReadConfig.NoRealtimeDataAlertDurationSeconds);
        }

        /// <summary>
        /// 返回软件已经运行的时间。
        /// </summary>
        private static TimeSpan UpTime
        {
            get { return DateTime.Now - Process.GetCurrentProcess().StartTime; }
        }
    }
}